#include "udpbroadcast.h"

static int counter = 0;
static char myIP[INET6_ADDRSTRLEN];
static int master = 0;
static pthread_t listenerthr;
static char serverIP[INET6_ADDRSTRLEN];
static char broadaddr[INET6_ADDRSTRLEN];

void *get_in_addr(struct sockaddr *sa);
void do_master(void);
void do_client(void);
void alarmHandler(int signum);
int sendUDPMessage(char address[INET6_ADDRSTRLEN], char* message);
void *listenerFunct(void *arg);

void *get_in_addr(struct sockaddr *sa)
{
	if (sa->sa_family == AF_INET) 
	{
		return &(((struct sockaddr_in*)sa)->sin_addr);
	}

	return &(((struct sockaddr_in6*)sa)->sin6_addr);
}



void alarmHandler(int signum)
{
	if(counter == 0)
	{
		sendUDPMessage(broadaddr, "master-reply");
		syslog (LOG_DEBUG,  "I am the master\n");
		master = 1;
	}
	else
	{
		
		while(strcmp(serverIP, "") == 0)
		{
		}
		syslog (LOG_DEBUG,  "I am the slave\n");

	}
}

int sendUDPMessage(char address[INET6_ADDRSTRLEN], char* message)
{
	int sockfd;
	int broadcast = 1;
	struct hostent *he;
	struct sockaddr_in dest_addr;
	int numbytes;

	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 
	{
		perror("socket");
		return 1;
	}

	// this call is the difference between this program and talker.c:
	if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof broadcast) == -1) 
	{
		perror("setsockopt (SO_BROADCAST)");
		return 1;
	}
	
	he = gethostbyname(address);
	dest_addr.sin_family = AF_INET;     // host byte order
	dest_addr.sin_port = htons(BCPORTI); // short, network byte order
	dest_addr.sin_addr = *((struct in_addr *)he->h_addr);
	memset(dest_addr.sin_zero, '\0', sizeof dest_addr.sin_zero);
	if ((numbytes=sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&dest_addr, sizeof dest_addr)) == -1) 
	{
		perror("sendto");
		return 1;
	}
	syslog (LOG_DEBUG,  "Sent %d bytes to %s\n", numbytes, inet_ntoa(dest_addr.sin_addr));

	return 0;
}

void *listenerFunct(void *arg)
{
	int sockfd;
	struct addrinfo hints, *servinfo, *p;
	int retval;
	int numbytes;
	struct sockaddr_storage their_addr;
	char buf[MAXBUFLEN];
	size_t addr_len;
	char s[INET6_ADDRSTRLEN];
	
	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_flags = AI_PASSIVE; // use my IP
	

/*
 * Get info on the port we are trying to bind to.
 */
	if ((retval = getaddrinfo(NULL, BCPORT, &hints, &servinfo)) == -1) 
	{
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(retval));
		exit(1);
	}
/*
 * Loop through the results and bind to the first available.
 */
	for(p = servinfo; p != NULL; p = p->ai_next) 
	{
		if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) 
		{
			perror("listener: socket");
			continue;
		}

		if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) 
		{
			close(sockfd);
			perror("listener: bind");
			continue;
		}
		break;
	}

/* 
 * Check if we bound p to a socket.
 */
	if (p == NULL) 
	{
		fprintf(stderr, "listener: failed to bind socket\n");
		exit(-1);
	}

	freeaddrinfo(servinfo);

/* 
 * Loop waits to receive a UDP message from other gate controllers within the network. 
 */
	while(1)
	{
		syslog (LOG_DEBUG,  "listener: waiting to recvfrom...\n");
		
		addr_len = sizeof their_addr;
		if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN-1 , 0, (struct sockaddr *)&their_addr, &addr_len)) == -1)
		{
			perror("recvfrom");
			exit(1);
		}
		inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr *)&their_addr), s, sizeof s);
		if(strcmp(myIP, s))
		{
			syslog (LOG_DEBUG,  "listener: got packet from %s\n", s); 
			syslog (LOG_DEBUG,  "listener: packet is %d bytes long\n", numbytes);
			buf[numbytes] = '\0';
			syslog (LOG_DEBUG,  "listener: packet contains \"%s\"\n", buf);
			//If reply is from the master then close the listener.
			if(strcmp(buf, "master-reply") == 0)
			{
				strcpy(serverIP, s);
				pthread_cancel(listenerthr);
			}
			/*
			 * A counter is used so that if we get any message, including broadcasts from
			 * other clients we know that we should not be the master.
			 * By giving up being the master it ensures that if multiple controllers come
			 * online at the same time only 1 will become the master.
			 */
			counter++;
			/*
			 * If it is the master then reply to the message received.
			 */
			if(master == 1)
			{
				syslog (LOG_DEBUG,  "Sending packet to: %s\n", s);
				sendUDPMessage(s, "master-reply");
			}
		}
	}
	close(sockfd);
	return 0;

}

int findMaster(char *ip, bool *m)
{
	char s[INET6_ADDRSTRLEN];
	int sockfd;
	struct ifreq ifr;
	struct sockaddr_in saddr;
	int fd;
	char *baddr;

	strcpy(broadaddr, "");
	/* 
	 * Specify a signal handler so that we can wait for master replies
	 * using an alarm.
	 */
	signal(SIGALRM, alarmHandler);
	
	fd = socket(PF_INET,SOCK_STREAM,0);
	strcpy(ifr.ifr_name, "eth0");
	ioctl(fd, SIOCGIFADDR, &ifr);
	saddr = *((struct sockaddr_in *)(&(ifr.ifr_addr)));

	/* Create listener thread */
	alarm(3);
	pthread_create(&listenerthr, NULL, listenerFunct, NULL);
	syslog (LOG_DEBUG,  "Broadcaster: %s\n",

	/*
	 * Converts the integer representation of our IP address into a string
	 * so that we can manipulate it.
	 */
	inet_ntop(saddr.sin_family, get_in_addr((struct sockaddr *)&saddr), s, sizeof s));

	/*
	 * Change the string of our IP address into a broadcast address.
	 */
	strcpy(myIP, s);
	baddr = strrchr(s, '.');
	strcpy(baddr, ".255\0");
	strcpy(broadaddr, s);
	syslog (LOG_DEBUG,  "ping %s\n",s);

	sendUDPMessage(s, "broadcast");
	
	/*
	 * While this thread is a client and the master has not been 
	 * found sleep to avoid busy waiting.
	 */
	while(strcmp(serverIP, "") == 0 && master != 1)
	{	
		sleep(1);
	}
	close(sockfd);
	
	/* 
	 * If empty then this thread is the master and should return 
	 * its own address and m true.
	 */
	if ( strcmp(serverIP, "") == 0 )
	{
		inet_ntop(saddr.sin_family, get_in_addr((struct sockaddr *)&saddr), s, sizeof s);

		strcpy(ip, s);
		m[0] = true;
	}
	else
	/*
	 * Else it is the slave so return the server ip and m false.
	 */
	{
		strcpy(ip, serverIP); 
		m[0] = false;
		
	}
	
	return 0;
}

